ForeignKeyNamingStrategy.java
package org.codefilarete.stalactite.dsl.naming;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Comparator;
import org.codefilarete.stalactite.query.model.JoinLink;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Key;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.tool.exception.Exceptions;
import org.codefilarete.tool.trace.MutableInt;
import static org.codefilarete.tool.collection.Iterables.first;
/**
* Contract for giving a name to a foreign key
*
* @author Guillaume Mary
*/
public interface ForeignKeyNamingStrategy {
String DEFAULT_FOREIGNKEY_PREFIX = "FK_";
Comparator<JoinLink<?, ?>> COLUMN_COMPARATOR = Comparator.comparing(JoinLink::getExpression);
<SOURCETABLE extends Table<SOURCETABLE>, TARGETTABLE extends Table<TARGETTABLE>, ID> String giveName(Key<SOURCETABLE, ID> src, Key<TARGETTABLE, ID> target);
ForeignKeyNamingStrategy HASH = new ForeignKeyNamingStrategy() {
@Override
public <SOURCETABLE extends Table<SOURCETABLE>, TARGETTABLE extends Table<TARGETTABLE>, ID> String giveName(Key<SOURCETABLE, ID> src, Key<TARGETTABLE, ID> target) {
MutableInt hashCode = new MutableInt(src.getTable().getAbsoluteName().hashCode());
hashCode.reset(hashCode.getValue() * 31 + target.getTable().getAbsoluteName().hashCode());
// We ensure a consistent ordering of columns, regardless of the order they were bound.
src.getColumns().stream().sorted(COLUMN_COMPARATOR).forEach(joinLink -> {
hashCode.reset(hashCode.getValue() * 31 + joinLink.getExpression().hashCode());
});
target.getColumns().stream().sorted(COLUMN_COMPARATOR).forEach(joinLink -> {
hashCode.reset(hashCode.getValue() * 31 + joinLink.getExpression().hashCode());
});
return DEFAULT_FOREIGNKEY_PREFIX + Integer.toHexString(hashCode.getValue());
}
};
/**
* Composed of a prefix, source table name, source column name, target column name.
* Must be quite unique to prevent Database such as HSQLDB to yel because key names uniqueness is over a table space, not per table.
*/
ForeignKeyNamingStrategy DEFAULT = new ForeignKeyNamingStrategy() {
@Override
public <SOURCETABLE extends Table<SOURCETABLE>, TARGETTABLE extends Table<TARGETTABLE>, ID> String giveName(Key<SOURCETABLE, ID> src, Key<TARGETTABLE, ID> target) {
if (src.getColumns().size() == 1) {
return DEFAULT_FOREIGNKEY_PREFIX + src.getTable().getName() + "_" + ((Column) first(src.getColumns())).getName()
+ "_" + target.getTable().getName() + "_" + ((Column) first(target.getColumns())).getName();
} else {
return HASH.giveName(src, target);
}
}
};
/**
* Generates same name as Hibernate (4.3.7) does. From org.hibernate.mapping.Constraint#generateName(String, Table, Column... columns)
*/
ForeignKeyNamingStrategy HIBERNATE_4 = new ForeignKeyNamingStrategy() {
@Override
public <SOURCETABLE extends Table<SOURCETABLE>, TARGETTABLE extends Table<TARGETTABLE>, ID> String giveName(Key<SOURCETABLE, ID> src, Key<TARGETTABLE, ID> target) {
// Use a concatenation that guarantees uniqueness, even if identical names
// exist between all table and column identifiers.
StringBuilder sb = new StringBuilder("table`" + src.getTable().getName() + "`");
// We ensure a consistent ordering of columns, regardless of the order they were bound.
target.getColumns().stream().sorted(COLUMN_COMPARATOR).forEach(column -> sb.append("column`").append(column.getExpression()).append("`"));
return "FK" + hashName(sb.toString());
}
};
/**
* Generates a hash for a foreign key represented by given arguments
* Implementation that returns the same hash as Hibernate 7 does.
* From org.hibernate.boot.model.naming.NamingHelper#generateHashedFkName(String, Identifier, Identifier, List<Identifier>...)
*/
ForeignKeyNamingStrategy HIBERNATE_7 = new ForeignKeyNamingStrategy() {
@Override
public <SOURCETABLE extends Table<SOURCETABLE>, TARGETTABLE extends Table<TARGETTABLE>, ID> String giveName(Key<SOURCETABLE, ID> src, Key<TARGETTABLE, ID> target) {
// Use a concatenation that guarantees uniqueness, even if identical names
// exist between all table and column identifiers.
StringBuilder sb = new StringBuilder("table`" + src.getTable().getName() + "`")
.append("references`").append(target.getTable().getName()).append("`");
// Note that referenced columns are not taken into account because the primary key is automatically targeted by database engines (same
// principle as Hibernate 7).
// We ensure a consistent ordering of columns, regardless of the order they were bound.
src.getColumns().stream().sorted(COLUMN_COMPARATOR).forEach(column -> sb.append("column`").append(column.getExpression()).append("`"));
return "FK" + hashName(sb.toString());
}
};
/**
* Same algorithm as Hibernate (4.3.7), see org.hibernate.mapping.Constraint#hashedName(String)
*
* @param s the string to be hashed
* @return a hashed (MD5) version of the given String, less than 30 characters
*/
static String hashName(String s) {
try {
@SuppressWarnings("java:S4790" /* No sensitive code related to MD5 usage : only used to hash foreign key name */)
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(s.getBytes());
byte[] digest = md.digest();
BigInteger bigInt = new BigInteger(1, digest);
// By converting to base 35 (full alphanumeric), we guarantee
// that the length of the name will always be smaller than the 30
// character identifier restriction enforced by a few dialects.
return bigInt.toString(35);
} catch (NoSuchAlgorithmException e) {
throw Exceptions.asRuntimeException(e);
}
}
}